import { join } from 'path';
import { readFileSync } from 'fs';
import { terminal } from 'terminal-kit';
import {
  convertPackageName,
  DependenciesDef, dependenciesMerge,
  installDeps,
  ProjectCreator,
  ProjectCreatorBasicOptions,
  ProjectCreatorBasicState,
  ProjectStructure,
} from 'create-ts-next';
import {
  AppDependencies,
  Dependencies,
  React17Dependencies,
  React18Dependencies,
  ViteDependencies,
} from './dependencies';

export interface CreateWebpackNextProjectOptions extends ProjectCreatorBasicOptions {
  react: string | '17' | '18',
  vite: boolean,
}

export interface CreateWebpackNextProjectState extends ProjectCreatorBasicState {

}

export class WebpackNextProjectCreator extends ProjectCreator<CreateWebpackNextProjectOptions, CreateWebpackNextProjectState> {

  constructor(opts: CreateWebpackNextProjectOptions) {
    super(opts, {});
  }

  async startUp(): Promise<this> {
    await this.detectPackageCmd();
    const { name } = this.options;
    terminal(`Create webpack project `).cyan(name);
    process.stdout.write('\n');

    if (this.packageCmd != null) {
      terminal('Packages manager used: ').cyan(this.packageCmd);
      process.stdout.write('\n');
    }

    process.stdout.write('\n');
    this.create(this.getStructure());
    process.stdout.write('\n');

    terminal('Project ').cyan(name);
    terminal(' created. ');

    let installed = false;

    if (this.packageCmd != null) {
      terminal('Install dependencies by ').cyan(this.packageCmd);
      terminal('... \n');
      try {
        await installDeps(this.projectRoot, this.packageCmd);
        installed = true;
      } catch (e) {
        //
      }
    }

    process.stdout.write('\n');

    if (this.packageCmd != null && installed) {
      terminal.green('All dependencies has been installed!\n\n');
    }

    terminal('You can try the follow commands:\n');
    terminal.blue(`cd ${name}`);
    process.stdout.write('\n');

    const cmd = this.options.packageManager === 'pnpm' ? this.options.packageManager : `${this.options.packageManager} run`;

    if (!installed) {
      terminal.blue(`${this.options.packageManager} install`);
      process.stdout.write('\n');
    }

    terminal.blue(`${cmd} dev-server`);
    if (this.options.vite) {
      terminal.gray(` or `);
      terminal.blue(`${cmd} dev-vite`);
    }

    process.stdout.write('\n');
    process.stdout.write('\n');
    return this;
  }

  getIndexFileData(): string {
    if (this.options.react === '17') {
      return readFileSync(this.getTemplatePath('src/index17.tsx')).toString();
    }
    return readFileSync(this.getTemplatePath('src/index.tsx')).toString();
  }

  getStructure(): ProjectStructure {
    const ignoreTpl = true;
    const structure = {
      name    : '',
      type    : 'dir',
      children: [
        {
          name    : 'config',
          type    : 'dir',
          children: [
            { name: 'webpack.module.js' },
            { name: 'webpack.optimization.js' },
            { name: 'webpack.plugins.js' },
            { name: 'webpack.resolve.js' },
          ],
        },
        {
          name    : 'src',
          type    : 'dir',
          children: [
            // files
            { name: 'global.css' },
            { name: 'global.d.ts' },
            { name: 'index.html', ignoreTpl },
            { name: 'index.tsx', ignoreTpl, data: this.getIndexFileData() },
            // folders
            {
              name    : 'app',
              type    : 'dir',
              children: [
                { name: 'App.tsx' },
                { name: 'AppService.ts' },
                { name: 'index.ts' },
              ],
            },
            {
              name    : 'components',
              type    : 'dir',
              children: [
                { name: 'index.ts' },
              ],
            },
            {
              name    : 'hooks',
              type    : 'dir',
              children: [
                { name: 'index.ts' },
              ],
            },
            {
              name    : 'pages',
              type    : 'dir',
              children: [
                { name: 'index.ts' },
                { name: 'Home.module.css' },
                { name: 'Home.tsx' },
              ],
            },
            {
              name    : 'svg',
              type    : 'dir',
              children: [
                { name: 'webpack-logo.svg', ignoreTpl },
              ],
            },
          ],
        },
        { name: 'package.json', data: this.generatePackageData() },
        { name: '.browserslistrc' },
        { name: '.env.defaults' },
        { name: '.env' },
        { name: '.eslintrc.js' },
        { name: '.prettierrc' },
        { name: '.gitignore' },
        { name: '.swcrc' },
        { name: 'postcss.config.js' },
        { name: 'svgr.config.js' },
        { name: 'svgo.config.js' },
        { name: 'postcss.config.js' },
        { name: 'windi.config.js' },
        { name: 'tsconfig.json' },
        { name: 'webpack.config.js' },
        // vite ? { name: 'vite.config.ts' } : undefined,
        // vite ? { name: 'index.html' } : undefined,
      ],
    };

    if (this.options.vite) {
      structure.children.push(
        { name: 'vite.config.ts' },
        { name: 'index.html' },
      );
    }

    return structure as ProjectStructure;
  }

  getTemplatePath(relative: string): string {
    return join(__dirname, `../template/${relative}.ejs`);
  }

  getDeps(): DependenciesDef | undefined {
    let deps: DependenciesDef | undefined = undefined;
    Object.values(Dependencies).forEach(lib => {
      deps = dependenciesMerge(deps, lib);
    });
    switch (this.options.react) {
      case '17' :
        deps = dependenciesMerge(deps, React17Dependencies);
        break;
      default :
        deps = dependenciesMerge(deps, React18Dependencies);
    }
    deps = dependenciesMerge(deps, AppDependencies);
    if (this.options.vite) {
      deps = dependenciesMerge(deps, ViteDependencies);
    }
    return deps;
  }

  generatePackageData(): string {
    const pkgScripts: Record<string, string> = {
      'dev-server': 'webpack-dev-server',
    };
    if (this.options.vite) {
      pkgScripts['dev-vite'] = 'vite';
    }
    pkgScripts['build'] = 'cross-env NODE_ENV=production webpack';

    const pkgData = {
      'name'       : convertPackageName(this.options.name),
      'version'    : '1.0.0',
      'description': '',
      'scripts'    : pkgScripts,
      'author'     : '',
      'license'    : 'UNLICENSED',
      'private'    : true,
      ...(this.getDeps() || {}),
    };
    return JSON.stringify(pkgData, null, 2);
  }
}
